#include "ThreadPool.h"

#include "Exception.h"
#include "Thread.h"
#include "thread/current_thread.h"
#include "utility/assertion.h"

#include <cinttypes>
#include <cstdlib>
#include <iostream>

ThreadPool::ThreadPool(std::size_t maxSize, std::string name):
    _tasks{},
    _notEmpty{},
    _notFull{},
    _mutex{},
    _name{std::move(name)},
    _initTask{default_func},
    _threads{},
    _maxSize{maxSize},
    _running{false} {}

ThreadPool::~ThreadPool() {
    if (_running) {
        stop();
    }
}

void ThreadPool::set_init_task(TrivialFunc task) {
    assert(task);
    _initTask = std::move(task);
}

void ThreadPool::start(std::size_t threadCount) {
    assert(_threads.empty());
    _running = true;
    _threads.reserve(threadCount);
    for (decltype(+threadCount) i = 0; i < threadCount; i++) {
        _threads.emplace_back(std::make_unique<Thread>(
            [this](){ _run_in_thread(); }, _name + std::to_string(i)));
        _threads.back()->start();
    }
    if (threadCount != 0) {
        return;
    }
    _initTask();
}

void ThreadPool::stop() {
    {
        std::unique_lock lock{_mutex};
        _running = false;
        _notEmpty.notify_all();
    }
    for (auto &t : _threads) {
        t->join();
    }
}

void ThreadPool::run(TrivialFunc task) {
    assert(task);
    if (_threads.empty()) {
        task();
        return;
    }
    std::unique_lock lock{_mutex};
    _notFull.wait(lock, [this](){ return !_is_full(); });
    assert(!_is_full());
    _tasks.emplace_back(std::move(task));
    _notEmpty.notify_one();
}


bool ThreadPool::_is_full() const {
    return _maxSize != 0 && _tasks.size() >= _maxSize;
}

void ThreadPool::_run_in_thread() {
#ifdef NDEBUG
    const auto *const name = _name.c_str();
    try {
        _thread_func();
    }
    catch (const Exception &e) {
        CurrentThread::set_thread_name("crashed-by-exception");
        ::fprintf(stderr, "Exception caught in Thread %s\n", name);
        ::fprintf(stderr, "what: %s\n", e.what());
        ::fprintf(stderr, "stack trace: %s\n", e.stack_trace());
        ::abort();
    }
    catch (const std::exception &e) {
        CurrentThread::set_thread_name("crashed-by-std::exception");
        ::fprintf(stderr, "stack trace: %s\n", CurrentThread::stack_trace(true).c_str());
        ::fprintf(stderr, "std::exception caught in Thread %s\n", name);
        ::fprintf(stderr, "what: %s\n", e.what());
        
        ::abort();
    }
    catch (...) {
        CurrentThread::set_thread_name("crashed");
        ::fprintf(stderr, "stack trace: %s\n", CurrentThread::stack_trace(true).c_str());
        ::fprintf(stderr, "unknow exception caught in Thread: %s\n", name);
        ::abort();
    }
#else
    _thread_func();
#endif
}

void ThreadPool::_thread_func() {
    _initTask();

    while (_running) {
        TrivialFunc task = default_func;
        {
            std::unique_lock lock{_mutex};
            _notEmpty.wait(lock, [this](){ return !(_running && _tasks.empty()); });
            if (!_tasks.empty()) {
                task = std::move(_tasks.front());
                _tasks.pop_front();
                if (_maxSize != 0) {
                    _notFull.notify_one();
                }
            }
        }
        task();
    }
    
}
